/*
 * Copyright (C) 2009 Leandro Aparecido <lehphyro@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.metalframework.persistence.repository.collection;

import java.util.*;

/**
 * Set implementation of {@link org.metalframework.persistence.repository.collection.PersistentCollection}.
 * 
 * @param <T> Type of the entity whose set represents the extent.
 * 
 * @since 1.0
 * @see org.metalframework.persistence.repository.collection.PersistentCollection
 * @see org.metalframework.persistence.repository.collection.PersistentCollectionStrategy
 */
public class PersistentSet<T> extends HashSet<T> implements PersistentCollection<T> {

	private static final long serialVersionUID = 3763895003029822261L;

	/**
	 * Strategy to use to interact with the repository.
	 */
	private PersistentCollectionStrategy<T> strategy;

	/**
	 * Creates a persistent set with no strategy.
	 */
	public PersistentSet() {
		this(null);
	}
	
	/**
	 * Creates a persistent set with the specified strategy.
	 * 
	 * @param strategy Strategy to interact with the repository.
	 */
	public PersistentSet(PersistentCollectionStrategy<T> strategy) {
		this.strategy = strategy;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean add(T entity) {
		if (contains(entity)) {
			return false;
		}
		if (strategy.contains(entity)) {
			return false;
		}
		strategy.add(size(), entity);
		
		return true;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	@SuppressWarnings("unchecked")
	public boolean remove(Object entity) {
		if (entity == null) {
			throw new IllegalArgumentException("Entity to remove cannot be null");
		}
		if (!getEntityType().isAssignableFrom(entity.getClass())) {
			throw new IllegalArgumentException("Entity to remove must be of type: " + getEntityType());
		}
		return strategy.remove((T)entity);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Iterator<T> iterator() {
		return new PersistentSetIterator();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int size() {
		return strategy.size();
	}

	/**
	 * {@inheritDoc}
	 */
	public void flush() {
		strategy.flush();
	}

	/**
	 * {@inheritDoc}
	 */
	public Class<T> getEntityType() {
		return strategy.getEntityType();
	}

	/**
	 * Gives access to the strategy.
	 * 
	 * @return Strategy being used by the collection.
	 */
	protected PersistentCollectionStrategy<T> getStrategy() {
		return strategy;
	}
	
	/**
	 * Iterator for {@link org.metalframework.persistence.repository.collection.PersistentSet<T><T>}.
	 * 
	 * @since 1.0
	 */
	class PersistentSetIterator implements Iterator<T> {
		/**
		 * Counter of the current position in the iteration.
		 */
		private int counter;
		
		/**
		 * Indicates if the current object has been removed.
		 */
		private boolean removed;
		
		/**
		 * {@inheritDoc}
		 */
		public boolean hasNext() {
			return counter < size();
		}

		/**
		 * {@inheritDoc}
		 */
		public T next() {
			if (!hasNext()) {
				throw new NoSuchElementException("No such element: " + counter);
			}
			removed = false;
			return strategy.get(counter++);
		}

		/**
		 * {@inheritDoc}
		 */
		public void remove() {
			if (counter == 0) {
				throw new IllegalStateException("Next has not been called");
			}
			if (removed) {
				throw new IllegalStateException("Remove must be called only once per iteration");
			}
			strategy.remove(counter - 1);
			removed = true;
		}
	}
}
